namespace Bowlus.FrontEnd.RequestRouting.ApplicationDeployment
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Bowlus.Common;

    using Microsoft.WindowsAzure.ServiceRuntime;

    public class BasicDeploymentStrategy : IApplicationDeploymentStrategy
    {
        public IEnumerable<ApplicationDeploymentRequest> AddApplication(TenantDeployment newDeployment, IEnumerable<ApplicationHost> appHosts)
        {
            var appHostsList = appHosts.ToList();

            // Host with the smallest number of apps will receive the app deployment
            var targetHost = (from appHost in appHostsList
                              select appHost).OrderByDescending(ah => ah.Deployments.Count()).LastOrDefault();

            if (targetHost == null)
            {
                throw new ApplicationException("There has to be at least one host.");
            }

            var targetInstance =
                RoleEnvironment.Roles[Settings.AppHostRoleName].Instances.FirstOrDefault(
                    i => targetHost.InstanceId.Equals(i.Id));

            if (targetInstance == null)
            {
                throw new ApplicationException(string.Format("No running instance with ID {0}.", targetHost.InstanceId));
            }

            return new List<ApplicationDeploymentRequest>
                {
                    new AddApplicationRequest(
                        newDeployment,
                        new ApplicationHost
                            {
                                ApphostHttpEndpoint =
                                    targetInstance.InstanceEndpoints[Settings.AppHostRoleName].IPEndpoint,
                                InstanceId = targetInstance.Id,
                                Status = ApplicationHostStatus.Running
                            })
                };
        }

        public IEnumerable<ApplicationDeploymentRequest> RemoveApplication(TenantDeployment deploymentToRemove, IEnumerable<ApplicationHost> appHosts)
        {
            var appHostList = appHosts.ToList();

        	var deploymentList =
        		appHostList.SelectMany(
        			ah => ah.Deployments.Select(d => new {Deployment = d, AppHostInstanceId = ah.InstanceId})).
        			ToList();

        	var deployment =
        		deploymentList.FirstOrDefault(dep => dep.Deployment.HostName.Equals(deploymentToRemove.HostName));

            if (deployment == null)
            {
                throw new ApplicationException(
                    string.Format("Application {0} is not found on a host", deploymentToRemove.HostName));
            }

            // Now construct a list of hosts with their deployments.
            //var appHosts = (from tenantDeployment in deploymentsList
            //                group tenantDeployment by tenantDeployment.AppHostInstanceId
            //                    into hostGroup
            //                    select
            //                        new
            //                        {
            //                            AppHostInstanceId = hostGroup.Key,
            //                            AppCount = hostGroup.Count(),
            //                            ApplicationDeployments =
            //                    deploymentsList.Where(dp => dp.HostName.Equals(hostGroup.Key))
            //                        }).ToList();

            // Will this make the number of apps on the host less than the average?
            //var appHost = appHosts.FirstOrDefault(h => h.AppHostInstanceId.Equals(appHostInstanceId));

            //if (appHost == null)
            //{
            //    throw new ApplicationException(string.Format("Application host {0} is not found.", appHostInstanceId));
            //}

            var deploymentRequests = new List<ApplicationDeploymentRequest>();

            var hostDepMgrRemovingTheAppFrom =
                RoleEnvironment.Roles[Settings.AppHostRoleName].Instances.First(i => i.Id.Equals(deployment.AppHostInstanceId)).InstanceEndpoints[Settings.AppHostRoleName];

            deploymentRequests.Add(
                new DeleteApplicationRequest(
                    deploymentToRemove,
                    new ApplicationHost
                    {
                        ApphostHttpEndpoint = hostDepMgrRemovingTheAppFrom.IPEndpoint,
                        InstanceId = deployment.AppHostInstanceId,
                        Status = ApplicationHostStatus.Running
                    }));
			
			return deploymentRequests;
        }

		public IEnumerable<ApplicationDeploymentRequest> MoveApplications(IEnumerable<ApplicationHost> appHosts)
		{
			var deploymentRequests = new List<ApplicationDeploymentRequest>();

			var appHostList = appHosts.ToList();

			var deploymentList =
				appHostList.SelectMany(
					ah => ah.Deployments.Select(d => new { Deployment = d, AppHostInstanceId = ah.InstanceId })).
					ToList();

			var newAvgAppsPerHost =
				Convert.ToInt16(Math.Floor((double)(deploymentList.Count) / appHostList.Count()));

			var crowdedHosts =
				(from host in appHostList
				 where host.Deployments.Count() > newAvgAppsPerHost
				 select host).ToList();

			var excessDeployments = new Dictionary<ApplicationHost, List<TenantDeployment>>();
			foreach (var host in crowdedHosts)
			{
				var appHostCurrentDeploymentCount = 
					host.Deployments.Count(dep => dep.Status.Equals(TenantDeploymentStatus.Running));

				excessDeployments.Add(host, host.Deployments.Take(appHostCurrentDeploymentCount - newAvgAppsPerHost).ToList());
			}

			var others = appHostList.Except(crowdedHosts).ToList();

			foreach (var host in others)
			{
				var appHostCurrentDeploymentCount =
					host.Deployments.Count(dep => dep.Status.Equals(TenantDeploymentStatus.Running));

				if (appHostCurrentDeploymentCount >= newAvgAppsPerHost) continue;

				// count is smaller than the average by definition
				var deficitCount = newAvgAppsPerHost - appHostCurrentDeploymentCount;
				foreach (var excessHost in excessDeployments.Keys)
				{
					var fromHostDepMgrEndPoint =
						RoleEnvironment.Roles[Settings.AppHostRoleName].Instances.First(
							i => i.Id.Equals(excessHost.InstanceId)).InstanceEndpoints[Settings.AppHostRoleName];

					while (excessDeployments[excessHost].Count > 0 && deficitCount > 0)
					{
						var td = excessDeployments[excessHost][0];
						deploymentRequests.Add(
							new MoveApplicationRequest(
								td,
								new ApplicationHost
								{
									ApphostHttpEndpoint = fromHostDepMgrEndPoint.IPEndpoint,
									InstanceId = excessHost.InstanceId,
									Status = ApplicationHostStatus.Running
								},
								null));

						deficitCount--;
						excessDeployments[excessHost].RemoveAt(0);
					}

					if (deficitCount == 0)
					{
						break;
					}
				}
			}

			return deploymentRequests;
		}

    	public IEnumerable<ApplicationDeploymentRequest> InitializeDeployments(IEnumerable<ApplicationHost> applicationHosts, IEnumerable<TenantDeployment> deployments)
        {
            var i = 0;
            var appHosts = applicationHosts.ToList();
            var numberOfAppHosts = appHosts.Count();

            if (numberOfAppHosts == 0)
            {
                return new List<ApplicationDeploymentRequest>();
            }

            var deploymentRequests = //new List<ApplicationDeploymentRequest>();
                deployments.Select(
                    tenantDeployment =>
                    new AddApplicationRequest(tenantDeployment, appHosts.ElementAt(i++ % numberOfAppHosts))).ToList();

            return deploymentRequests;
        }

        public IEnumerable<ApplicationDeploymentRequest> AddHost(ApplicationHost newHost, IEnumerable<ApplicationHost> appHosts)
        {
            var appHostList = appHosts.ToList();

            var deployedApplications = appHostList.Sum(ah => ah.Deployments.Count());

            // find the new average number of deployments with the new addition
            var newAvgNumberOfDeployments =
                Convert.ToInt16(Math.Ceiling((double)deployedApplications / (appHostList.Count + 1)));

            var deploymentRequests = new List<ApplicationDeploymentRequest>();

            // Move that many apps to the new host
            var movedApps = 0;
            while (movedApps < newAvgNumberOfDeployments)
            {
                foreach (var host in appHostList)
                {
                    var appCountToMove = host.Deployments.Count() > newAvgNumberOfDeployments
                                             ? host.Deployments.Count() - newAvgNumberOfDeployments
                                             : 0;

                    var appsToMove = host.Deployments.Take(appCountToMove);

                    var fromHostDepMgrEndPoint =
                        RoleEnvironment.Roles[Settings.AppHostRoleName].Instances.First(
                            ins => ins.Id.Equals(host.InstanceId)).InstanceEndpoints[Settings.AppHostRoleName];

                    var fromHost = new ApplicationHost
                    {
                        ApphostHttpEndpoint = fromHostDepMgrEndPoint.IPEndpoint,
                        InstanceId = host.InstanceId,
                        Status = ApplicationHostStatus.Running
                    };

                    foreach (var tenantDeployment in appsToMove)
                    {
                        deploymentRequests.Add(new MoveApplicationRequest(tenantDeployment, fromHost, newHost));
                        movedApps++;
                    }
                }
            }

            return deploymentRequests;
        }

        public IEnumerable<ApplicationDeploymentRequest> RemoveHost(ApplicationHost hostToRemove, IEnumerable<ApplicationHost> appHosts)
        {
            var appHostList = appHosts.ToList();

            var i = 0;

            var appHost = appHostList.First(ah => ah.InstanceId.Equals(hostToRemove.InstanceId));

            if (appHost == null)
            {
                throw new ApplicationException(
                 string.Format("Host {0} is not found.", hostToRemove.InstanceId));
            }

            var remaningHosts = (from host in appHostList
                                 where !host.InstanceId.Equals(hostToRemove.InstanceId)
                                 select host).ToList();

            var deploymentRequests =
                appHost.Deployments.Select(
                    tenantDeployment =>
                    new AddApplicationRequest(tenantDeployment, remaningHosts.ElementAt(i++ % remaningHosts.Count))).ToList();

            return deploymentRequests;
        }

		public ApplicationHost Dictionary { get; set; }
	}
}